/*
 * Copyright (C) 2015-present, Wei Chou (weichou2010@gmail.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.wei.c.persist;

import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

import android.content.Context;
import android.content.SharedPreferences;
import android.text.TextUtils;

/**
 * @author Wei.Chou(weichou2010@gmail.com) 2015/12/2
 */
public class Keeper {
    protected static final String KEEP_XML_DEF = "keeper";

    private final String mKeepXml;
    private final SharedPreferences mSPref;

    private Keeper() {
        mKeepXml = null;
        mSPref = null;
    }

    private Keeper(Context context, String keepXml, boolean multiProcess) {
        mKeepXml = keepXml;
        mSPref = multiProcess ? SPrefHelper.multiProcess().getSPref(context, mKeepXml) :
                SPrefHelper.def().getSPref(context, mKeepXml);
    }

    private static void assertKeepXml(final String keepXml) {
        if (TextUtils.isEmpty(keepXml)) throw new IllegalArgumentException("keepXml should not be null");
    }

    private static void assertKey(final String key) {
        if (TextUtils.isEmpty(key)) throw new IllegalArgumentException("key should not be null");
    }

    public String getKeepXml() {
        return mKeepXml;
    }

    public SharedPreferences getSharedPreferences() {
        return mSPref;
    }

    public SharedPreferences.Editor edit() {
        return mSPref.edit();
    }

    public static abstract class Wrapper extends Keeper {
        protected final Context mContext;
        private Keeper mKeeperMultiProcess;

        private static final Map<String, WeakReference<WrapperImpl>> sName2KeeperMap
                = new HashMap<String, WeakReference<WrapperImpl>>();

        protected static WrapperImpl get(Context context, String keepXml) {
            assertKeepXml(keepXml);
            final String keepXmlLC = keepXml.toLowerCase();
            WeakReference<WrapperImpl> ref = sName2KeeperMap.get(keepXmlLC);
            WrapperImpl instance = ref == null ? null : ref.get();
            if (instance == null) {
                synchronized (Wrapper.class) {
                    ref = sName2KeeperMap.get(keepXmlLC);
                    instance = ref == null ? null : ref.get();
                    if (instance == null) {
                        instance = new WrapperImpl(context, keepXml);
                        sName2KeeperMap.put(keepXmlLC, new WeakReference<WrapperImpl>(instance));
                    }
                }
            }
            return instance;
        }

        // 仅为了保持{@link Wrapper}为abstract的，而{@link #get(Context, String)}又要为其实例化。
        public static class WrapperImpl extends Wrapper {
            private WrapperImpl(Context context, String keepXml) {
                super(context, keepXml);
            }

            /**
             * 获取根据地区语言不同而隔离的{@link Wrapper}实例。
             */
            // 返回{@link Wrapper}类型是为了避免再次出现本法。这里非常重要。
            public Wrapper withLocale() {
                return get(mContext, getKeepXml() + "-" + Locale.getDefault().toString());
            }
        }

        /**
         * 仅为子类提供默认构造方法。
         **/
        protected Wrapper() {
            mContext = null;
        }

        private Wrapper(Context context, String keepXml) {
            super(context, keepXml, false);
            mContext = context;
        }

        /**
         * 提供系统默认的{@link SharedPreferences}实例。
         **/
        // 返回{@link Keeper}类型是为了避免再次出现本法。
        public final Keeper def() {
            return this;
        }

        /**
         * 提供支持多进程的{@link SharedPreferences}实例。
         **/
        // 返回{@link Keeper}类型是为了避免再次出现本法。
        public final Keeper multiProcess() {
            if (mKeeperMultiProcess == null) {
                synchronized (this) {
                    if (mKeeperMultiProcess == null) {
                        mKeeperMultiProcess = new Keeper(mContext, getKeepXml(), true);
                    }
                }
            }
            return mKeeperMultiProcess;
        }
    }

    public Keeper keepInt(String key, int value) {
        assertKey(key);
        edit().putInt(key, value).apply();
        return this;
    }

    public int readInt(String key) {
        return readInt(key, -1);
    }

    public int readInt(String key, int defaultValue) {
        assertKey(key);
        return mSPref.getInt(key, defaultValue);
    }

    public Keeper keepBoolean(String key, boolean value) {
        assertKey(key);
        edit().putBoolean(key, value).apply();
        return this;
    }

    public boolean readBoolean(String key) {
        return readBoolean(key, false);
    }

    public boolean readBoolean(String key, boolean defaultValue) {
        assertKey(key);
        return mSPref.getBoolean(key, defaultValue);
    }

    public Keeper keepFloat(String key, float value) {
        assertKey(key);
        edit().putFloat(key, value).apply();
        return this;
    }

    public float readFloat(String key) {
        return readFloat(key, -1);
    }

    public float readFloat(String key, float defaultValue) {
        assertKey(key);
        return mSPref.getFloat(key, defaultValue);
    }

    public Keeper keepLong(String key, long value) {
        assertKey(key);
        edit().putLong(key, value).apply();
        return this;
    }

    public long readLong(String key) {
        return readLong(key, -1);
    }

    public long readLong(String key, long defaultValue) {
        assertKey(key);
        return mSPref.getLong(key, defaultValue);
    }

    public Keeper keepString(String key, String value) {
        assertKey(key);
        edit().putString(key, value).apply();
        return this;
    }

    public String readString(String key) {
        return readString(key, null);
    }

    public String readString(String key, String defaultValue) {
        assertKey(key);
        return mSPref.getString(key, defaultValue);
    }

    public Keeper remove(String key) {
        assertKey(key);
        edit().remove(key).apply();
        return this;
    }

    public boolean contains(String key) {
        assertKey(key);
        return mSPref.contains(key);
    }
}
